package com.usefullc.system.service;

import cn.hutool.core.util.ClassUtil;
import com.google.common.collect.Lists;
import com.usefullc.common.exception.BizException;
import com.usefullc.system.configuration.BaseDao;
import com.usefullc.system.configuration.TransactionPersistBizDao;
import com.usefullc.system.entity.BaseEntity;
import com.usefullc.system.entity.Pager;
import com.usefullc.system.query.BaseQuery;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;

import javax.annotation.PostConstruct;
import javax.persistence.Column;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

//@Transactional
public abstract class AbstractBizService<T extends BaseEntity,Q extends BaseQuery> extends AbstractService {

    public BaseDao<T, Serializable> baseDao;

//    @Autowired
//    protected D bizDao;
//
//    public D getBizDao(){
//        return bizDao;
//    }


    @PersistenceContext
    public EntityManager entityManager;

    @Autowired
    private TransactionPersistBizDao<T> transactionPersistBizDao;

    public AbstractBizService() {
    }


    private Class<T> getEntityClass(){
        Type type = getClass().getGenericSuperclass();
        Class<T> clazz = (Class<T>) ((ParameterizedType) type).getActualTypeArguments()[0];
        return clazz;
    }

    @PostConstruct
    public void init() {
        Class<T> entityClass = this.getEntityClass();
        this.baseDao = new BaseDao<>(entityClass, this.entityManager);
        transactionPersistBizDao.init(entityClass,this.baseDao);
    }

    public abstract List<Predicate> getPredicates(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder, Q q);

    private List<Predicate> getThisPredicates(Root<T> root, CriteriaQuery<?> q, CriteriaBuilder criteriaBuilder, Q query){
        List<Predicate> predicates = getPredicates(root, q, criteriaBuilder, query);
        try{
            Field[] declaredFields = FieldUtils.getAllFields(this.getEntityClass());
            for (Field declaredField : declaredFields) {
                Field queryField = FieldUtils.getField(query.getClass(),declaredField.getName(),true);
                if(queryField == null){
                    continue;
                }
                if(StringUtils.equalsIgnoreCase(queryField.getName(),"serialVersionUID")){
                    continue;
                }
                queryField.setAccessible(true);
                Object fieldValue = queryField.get(query);
                if(fieldValue != null && !fieldValue.toString().trim().equals("")){
                    String column = declaredField.getName();
                    Column columnField = declaredField.getAnnotation(Column.class);
                    if(columnField != null && StringUtils.isNotEmpty(columnField.name())){
                        column = columnField.name();
                        if(column.startsWith("[") || column.startsWith("`")){
                            column = StringUtils.substring(column,1,column.length()-1);
                        }
                    }
                    predicates.add(criteriaBuilder.equal(root.get(column), fieldValue));
                }
                queryField.setAccessible(false);
            }

        }catch (Exception e){
            throw new BizException(e);
        }

        if (CollectionUtils.isEmpty(predicates)) {
            return Lists.newArrayList();
        }

//        if (q.getDelState() != null) {
//            predicates.add(criteriaBuilder.equal(root.get("delState"), q.getDelState()));
//        }
//        if (q.getUserId() != null) {
//            predicates.add(criteriaBuilder.equal(root.get("userId"), q.getUserId()));
//        }
        return predicates;
    }

    public T save(T entity) {
        return this.transactionPersistBizDao.save(entity);
    }

    public T saveWithoutTx(T entity) {
        return this.transactionPersistBizDao.saveWithoutTx(entity);
    }

    public List<T> saveAll(Iterable<T> entityList) {
        return this.transactionPersistBizDao.saveAll(entityList);
    }

    public void deletePhysByIds(List<Long> ids) {
        List<Serializable> newIds = new ArrayList<>();
        for (Long id : ids) {
            newIds.add(id);
        }
        this.transactionPersistBizDao.deletePhysByIds(newIds);
    }

    public void deletePhysById(Long id) {
      this.transactionPersistBizDao.deletePhysById(id);
    }

    public void delete(List<Long> ids) {
        this.transactionPersistBizDao.delete(ids);
    }
    public void delete(Long id) {
        List<Long> ids = new ArrayList<>();
        ids.add(id);
        this.transactionPersistBizDao.delete(ids);
    }


    public int executeUpdate(String sql){
        return this.transactionPersistBizDao.executeUpdate(sql);
    }

    public T findById(Serializable id) {
        Optional<T> optional =  this.baseDao.findById(id);
        if (optional.isPresent()) {
            return optional.get();
        }
        return null;
    }

    public T findOne(Q q) {
        if(q.getResultLimit() != null){
            if(q.getResultLimit() != 1){
                throw new BizException("findOne limit must be 1");
            }
            List<T> dataList = this.pageQuery(q);
            if(CollectionUtils.isNotEmpty(dataList)){
                return dataList.get(0);
            }else{
                return null;
            }
        }else{
            return this.baseDao.findOne((Specification<T>) (root, query, criteriaBuilder) -> {
                List<Predicate> predicates = getThisPredicates(root, query, criteriaBuilder, q);
                return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
            }).orElse(null);
        }
    }

    public List<T> findAll(Q q) {
        if(q.getResultLimit() != null){
           return this.pageQuery(q);
        }else{
            List<Sort.Order> orderList = wrapOrder(q);
            return this.baseDao.findAll((root, query, criteriaBuilder) -> {
                List<Predicate> predicates = getThisPredicates(root, query, criteriaBuilder, q);
                return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
            }, Sort.by(orderList));
        }
    }

    public Pager<T> findByPage(Q query) {
        if (query.getPageIndex() <= 0) {
            query.setPageIndex(1);
        }
        Pager<T> pager = new Pager<>(query.getPageIndex(), query.getPageSize());
        //count
        long count = count(query);
        if (count == 0) {
            return pager;
        } else {
            pager.setTotal(count);
        }
        //dataList
        pager.setDataList(pageQuery(query));
        return pager;

    }

    /**
     * 分页查询
     * @param query
     * @return
     */
    public List<T> pageQuery(Q query) {
        List<Sort.Order> orderList = wrapOrder(query);
        Pageable pageable = PageRequest.of(query.getPageIndex() - 1, query.getPageSize(), Sort.by(orderList));
        Page<T> page = this.baseDao.findAll((Specification<T>) (root, q, criteriaBuilder) -> {
            List<Predicate> predicates = getThisPredicates(root, q, criteriaBuilder, query);
            return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
        }, pageable);

        return page.getContent();
    }

    /**
     * 统计
     * @param query
     * @return
     */
    public long count(Q query) {
        long count = this.baseDao.count((root, q, criteriaBuilder) -> {
            List<Predicate> predicates = getThisPredicates(root, q, criteriaBuilder, query);
            return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
        });
        return count;
    }

    /**
     * 组装order
     * @param query
     * @return
     */
    private List<Sort.Order> wrapOrder(Q query) {
        String orderByColumn = query.getOrderByColumn();
        String ascOrDesc = query.getAscOrDesc();
        String[] orderByColumnArray = orderByColumn.split(",");
        String[] ascOrDescArray = ascOrDesc.split(",");
        List<Sort.Order> orderList = new ArrayList<>();
        for (int i = 0; i < orderByColumnArray.length; i++) {
            String orderByValue = orderByColumnArray[i];
            String ascOrDescValue = ascOrDescArray[i];
            orderList.add(new Sort.Order(Sort.Direction.fromString(ascOrDescValue), orderByValue));
        }

        return orderList;
    }



}
